﻿#region Copyright (c) 2011, thinktecture (http://www.thinktecture.com)

/*
   thinktecture.UIAnnotations was developed by Jörg Neumann.
   Copyright (c) 2011, thinktecture (http://www.thinktecture.com).
   All rights reserved, comes as-is and without any warranty. Use of this
   source file is governed by the license which is contained in LICENSE.TXT 
   in the distribution.
*/

#endregion

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace Thinktecture.DataForm.Controls
{
    [TemplatePart(Name = "PART_TextBox", Type = typeof(TextBox))]
    [TemplatePart(Name = "PART_DownButton", Type = typeof(Button))]
    [TemplatePart(Name = "PART_DownButton", Type = typeof(Button))]
    public class NumericUpDown : Control
    {

        #region Private Variables

        private TextBox _textBox;
        private Button _upButton;
        private Button _downButton;

        #endregion

        #region Constructors

        static NumericUpDown()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
        }
        public NumericUpDown()
        {

        }

        #endregion

        #region Overrides & Event Handling

        public override void OnApplyTemplate()
        {
            _textBox = this.Template.FindName("PART_TextBox", this) as TextBox;
            _upButton = this.Template.FindName("PART_UpButton", this) as Button;
            _downButton = this.Template.FindName("PART_DownButton", this) as Button;
            if (_upButton != null && _downButton != null)
            {
                _upButton.Click += this.OnButtonClick;
                _downButton.Click += this.OnButtonClick;
            }
            if (_textBox != null)
            {
                _textBox.GotFocus += this.OnGotFocus;
                _textBox.LostFocus += this.OnLostFocus;
                _textBox.PreviewTextInput += this.OnPreviewTextInput;
                _textBox.KeyUp += this.OnKeyUp;
            }
            this.GotFocus += this.OnControlGotFocus;
            this.SetBinding(true);
            this.RaiseEvent(new RoutedEventArgs(NumericUpDown.ValueChangedEvent, this));
            if (!this.IsEnabled)
                this.ShowButtons = false;
            base.OnApplyTemplate();
        }

        private void OnControlGotFocus(object sender, RoutedEventArgs e)
        {
            if (_textBox != null)
                _textBox.Focus();
        }

        private void OnKeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Up)
                this.ChangeValue(true, false);
            else if (e.Key == Key.Down)
                this.ChangeValue(false, false);
            else if (e.Key == Key.PageUp)
                this.ChangeValue(true, true);
            else if (e.Key == Key.PageDown)
                this.ChangeValue(false, true);
        }

        private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            string text = e.Text;
            double d;
            if (!double.TryParse(text, out d))
                e.Handled = true;
        }

        private void OnLostFocus(object sender, RoutedEventArgs e)
        {
            this.SetBinding(true);
        }

        private void OnGotFocus(object sender, RoutedEventArgs e)
        {
            this.SetBinding(false);
        }

        private void OnButtonClick(object sender, RoutedEventArgs e)
        {
            this.ChangeValue(sender == _upButton, false);
        }

        #endregion

        #region Private Functions

        #region ChangeValue

        private void ChangeValue(bool stepUp, bool largeStep)
        {
            if (this.GetValue(ValueProperty) == null) return;
            if (this.IsReadOnly || !this.IsEnabled) return;
            var current = (double)this.GetValue(ValueProperty);
            var step = (double)GetValue(largeStep ? LargeStepProperty : SmallStepProperty);
            var min = (double)GetValue(MinimumProperty);
            var max = (double)GetValue(MaximumProperty);
            if (stepUp && current < Maximum)
            {
                this.SetValue(ValueProperty, current + step);
            }
            else if (current > Minimum)
            {
                this.SetValue(ValueProperty, current - step);
            }
        }

        #endregion

        #region SetBinding

        private void SetBinding(bool displayFormat)
        {
            if (_textBox != null)
            {
                BindingOperations.ClearBinding(_textBox, TextBox.TextProperty);
                var bind = new Binding
                {
                    Source = this,
                    Path = new PropertyPath(ValueProperty),
                    StringFormat = (displayFormat) ? this.DisplayFormatString : this.EditFormatString,
                    Mode = BindingMode.TwoWay,
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    ValidatesOnExceptions = true
                };
                _textBox.SetBinding(TextBox.TextProperty, bind);
            }
        }

        #endregion

        #endregion

        #region Properties

        #region Value

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double?), typeof(NumericUpDown), new FrameworkPropertyMetadata(
                null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, OnValuePropertyChanged, OnCoerceValue));
        public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }

        private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var ctl = sender as NumericUpDown;
            if (ctl.IsLoaded)
                ctl.RaiseEvent(new RoutedEventArgs(NumericUpDown.ValueChangedEvent, ctl));
        }

        private static object OnCoerceValue(DependencyObject sender, object baseValue)
        {
            return ((NumericUpDown)sender).OnCoerceValueCore(sender, baseValue);
        }

        private object OnCoerceValueCore(DependencyObject sender, object baseValue)
        {
            if (baseValue == null)
                return baseValue;
            var current = (double)baseValue;
            var min = (double)GetValue(MinimumProperty);
            var max = (double)GetValue(MaximumProperty);
            if (current < min)
                return min;
            if (current > max)
                return max;
            return baseValue;
        }

        #endregion

        #region DisplayFormatString

        public static readonly DependencyProperty DisplayFormatStringProperty =
            DependencyProperty.Register("DisplayFormatString", typeof(string), typeof(NumericUpDown), new UIPropertyMetadata("", OnDisplayFormatStringPropertyChanged));
        public string DisplayFormatString { get { return (string)GetValue(DisplayFormatStringProperty); } set { SetValue(DisplayFormatStringProperty, value); } }

        private static void OnDisplayFormatStringPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((NumericUpDown)sender).OnDisplayFormatStringChanged(sender, e);
        }

        private void OnDisplayFormatStringChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            this.SetBinding(true);
        }

        #endregion

        #region EditFormatString

        public static readonly DependencyProperty EditFormatStringProperty =
            DependencyProperty.Register("EditFormatString", typeof(string), typeof(NumericUpDown), new UIPropertyMetadata(""));
        public string EditFormatString { get { return (string)GetValue(EditFormatStringProperty); } set { SetValue(EditFormatStringProperty, value); } }

        #endregion

        #region Minimum

        public static readonly DependencyProperty MinimumProperty =
            DependencyProperty.Register("Minimum", typeof(double), typeof(NumericUpDown), new UIPropertyMetadata(0d));
        public double Minimum { get { return (double)GetValue(MinimumProperty); } set { SetValue(MinimumProperty, value); } }

        #endregion

        #region Maximum

        public static readonly DependencyProperty MaximumProperty =
            DependencyProperty.Register("Maximum", typeof(double), typeof(NumericUpDown), new UIPropertyMetadata(100d));
        public double Maximum { get { return (double)GetValue(MaximumProperty); } set { SetValue(MaximumProperty, value); } }

        #endregion

        #region SmallStep

        public static readonly DependencyProperty SmallStepProperty =
            DependencyProperty.Register("SmallStep", typeof(double), typeof(NumericUpDown), new UIPropertyMetadata(1d));
        public double SmallStep { get { return (double)GetValue(SmallStepProperty); } set { SetValue(SmallStepProperty, value); } }

        #endregion

        #region LargeStep

        public static readonly DependencyProperty LargeStepProperty =
            DependencyProperty.Register("LargeStep", typeof(double), typeof(NumericUpDown), new UIPropertyMetadata(5d));
        public double LargeStep { get { return (double)GetValue(LargeStepProperty); } set { SetValue(LargeStepProperty, value); } }

        #endregion

        #region IsReadOnly

        public static readonly DependencyProperty IsReadOnlyProperty =
            DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(NumericUpDown), new UIPropertyMetadata(false));
        public bool IsReadOnly { get { return (bool)GetValue(IsReadOnlyProperty); } set { SetValue(IsReadOnlyProperty, value); } }

        #endregion

        #region ShowButtons

        public static readonly DependencyProperty ShowButtonsProperty =
            DependencyProperty.Register("ShowButtons", typeof(bool), typeof(NumericUpDown), new UIPropertyMetadata(true));
        public bool ShowButtons { get { return (bool)GetValue(ShowButtonsProperty); } set { SetValue(ShowButtonsProperty, value); } }

        #endregion

        #region ValueAlignment

        public static readonly DependencyProperty ValueAlignmentProperty =
            DependencyProperty.Register("ValueAlignment", typeof(TextAlignment), typeof(NumericUpDown), new UIPropertyMetadata(TextAlignment.Right));
        public TextAlignment ValueAlignment { get { return (TextAlignment)GetValue(ValueAlignmentProperty); } set { SetValue(ValueAlignmentProperty, value); } }

        #endregion

        #endregion

        #region Events

        #region ValueChanged

        public static readonly RoutedEvent ValueChangedEvent =
            EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(NumericUpDown));
        public event RoutedEventHandler ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        #endregion

        #endregion

    }
}
